<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>ChucK + JS => ChucKScript</title>
    <link rel="stylesheet" href="../../css/editor.css">
    <style>
        #midi {
            margin: 0px 15px;
        }
    </style>
  </head>
  <body onresize="chuckEditor.resize();">
<div>
    <div id="program">// MIDI 1: Note on / off events
global Event midiMessage;
global int midiCommand;
global int midiNote;
global int midiVelocity;

TriOsc t => ADSR adsr => dac;
adsr.set( 0.1::second, 0.3::second, 0.5, 0.7::second );
false => int alreadyOn;
0 => int mostRecentOn;

fun void RespondToMidi()
{
    while( true )
    {
        midiMessage => now;
        if( midiCommand >= 144 && midiCommand < 160 )
        {
            if( midiVelocity > 0 )
            {
                NoteOn( midiNote, midiVelocity );
            }
            else
            {
                NoteOff( midiNote );
            }
        }
        else if( midiCommand >= 128 && midiCommand < 144 )
        {
            NoteOff( midiNote );
        }
        else
        {
            <<< "unknown midi command:", midiCommand, midiNote, midiVelocity >>>;
        }
    }
}
spork ~ RespondToMidi();

fun void NoteOn( int note, int velocity )
{
    <<< "on:", note, velocity >>>;
    note => Std.mtof => float newFreq;
    velocity * 1.0 / 127.0 => float newGain;
    
    if( !alreadyOn )
    {
        adsr.keyOn(1);
        newFreq => t.freq;
        newGain => adsr.gain;
        note => mostRecentOn;
        true => alreadyOn;
    }
    else
    {
        spork ~ SlideFreq( newFreq, 0.05::second );
        spork ~ SlideGain( newGain, 0.05::second );
    }
}

fun void NoteOff( int note )
{
    <<< "off:", note >>>;   
    if( note == mostRecentOn )
    {
        adsr.keyOff(1);
        false => alreadyOn;
        0 => mostRecentOn;
    }
}


fun void SlideFreq( float newFreq, dur slideTime )
{
    t.freq() => float oldFreq;
    1000 => int numSteps;
    slideTime / numSteps => dur delta;
    ( newFreq - oldFreq ) / numSteps => float freqDelta;
    for( int i; i < numSteps; i++ )
    {
        oldFreq + freqDelta * i => t.freq;
        delta => now;
    }
    newFreq => t.freq;
}

fun void SlideGain( float newGain, dur slideTime )
{
    t.gain() => float oldGain;
    1000 => int numSteps;
    slideTime / numSteps => dur delta;
    ( newGain - oldGain ) / numSteps => float gainDelta;
    for( int i; i < numSteps; i++ )
    {
        oldGain + gainDelta * i => t.gain;
        delta => now;
    }
    newGain => t.gain;
}

while( true )
{
    1::second => now;
}                                
</div>
</div>
<div class="buttons">
    <button class="chuckButton" id="startChuck" disabled="disabled">Start ChucK</button>
    <button class="chuckButton" id="compileButton" disabled="disabled">Compile and Run</button>
    <button class="chuckButton" id="replaceButton" disabled="disabled">Replace</button>
    <button class="chuckButton" id="removeButton" disabled="disabled">Remove</button>
    <button class="chuckButton" id="clearButton" disabled="disabled">Clear VM and Globals</button>
    <button class="chuckButton" id="micButton" disabled="disabled">Enable Microphone</button>
    <button class="chuckButton" id="fileFormButton">Show File Uploader</button>
</div>
<div id="fileForm" class="hidden">
    <p>File name to use in ChucK: <input type="text" id="uploadFilename"></p>
    <p>Local file to upload: <input type="file" id="uploadFile" name="uploadFile"></p>
    <p><button class="chuckButton" id="fileButton" disabled="disabled">Upload File to Virtual File System</button></p>
</div>
<div id="midi">
    <p>Click the desired MIDI device:</p>
    <p>(Please note, the Web MIDI API is not implemented yet in Firefox or Safari.)</p>
    <ul id="midi-list">

    </ul>
</div>
<div>
    <textarea id="output"></textarea>
    <div id="shreds">
        <table id="shredstable">
            <col width="60px">
            <col width="1*">
            <col width="50px">
            <col width="60px">
            <tr><th>shred</th><th>code</th><th>time</th><th>remove</th>
        </table>
    </div>
    <div class="clearfix"></div>
</div>
    <script type="text/javascript" src="../../js/ace-min-noconflict/ace.js" charset="utf-8"></script>
    <script type="text/javascript" src="../../js/editor.js"></script>
    <script type="text/javascript" src="../../js/defer.js"></script>
    <script>
        // override
        var whereIsChuck = "../../js";        
    </script>
    <script type="text/javascript" src="../../js/webchuck_host.js"></script>
    <script type="text/javascript" src="../../js/host.js"></script>
    <script>
        // make editor a bit smaller
        chuckEditor.setOptions({
            maxLines: 20,
            minLines: 20
        }); 

        // modified from: https://codepen.io/kulak-at/pen/mxVNbx
        const list = document.getElementById('midi-list');

        function connectToDevice(device) 
        {
            chuckPrint( 'Connecting to device:', device.name, "(" + device.manufacturer + ")" );
            device.onmidimessage = function(m) 
            {
                const [command, key, velocity] = m.data;
                theChuck.setInt( "midiCommand", command );
                theChuck.setInt( "midiNote", key );
                theChuck.setInt( "midiVelocity", velocity );
                theChuck.broadcastEvent( "midiMessage" ); 
            }
        }

        function replaceElements(inputs) {
            while(list.firstChild) {
                list.removeChild(list.firstChild)
            }
            const elements = inputs.map(e => {
                console.log(e);
                const el = document.createElement('li')
                el.innerHTML = `<a href="javascript:;">${e.name} (${e.manufacturer})</a>`;
                el.addEventListener('click', connectToDevice.bind(null, e));
                return el;
            });

            elements.forEach(e => list.appendChild(e));
        }

        theChuckReady.then( function() {
            navigator.requestMIDIAccess().then(function(access) 
            {
                console.log('access', access);
                replaceElements(Array.from(access.inputs.values()));
                access.onstatechange = function(e) {
                    replaceElements(Array.from(this.inputs.values()));
                }

            });
        });

    </script>
  </body>
</html>


